
#include <string.h>

#include "util.h"

#include "PrimitiveMethod.h"
#include "Class.h"
#include "Closure.h"
#include "Image.h"

PrimitiveMethod::PrimitiveMethod(Image* image,string selector,Class* cls,int argv,Object* (*primitive)(Image*,Object*,int argc,Object** argv))
                  :Method(image,selector,cls){
   PrimitiveMethod::primitive=primitive;
   for(int i=0;i<argv;i++){
       args.push_back(NULL);
   }
}
Object* PrimitiveMethod::execute(Image* i,Object* receiver,int argc,Object** argv){
   return primitive(i,receiver,argc,argv);
}
#define PRIMITIVE_MATHOP_DEF(name,code)\
Object* name(Image* image,Object* receiver,int argc,Object** argv){\
    if(argc!=1) printf("ERROR: Mathop methods expect one argument\n");\
   int* plvalue=(int*)receiver->dataAt(INT_OBJECT_DATA_OFFSET);\
   int* prvalue=(int*)argv[0]->dataAt(INT_OBJECT_DATA_OFFSET);\
   int lvalue=*plvalue;\
   int rvalue=*prvalue;\
   int result=code;\
   Object* resultObject=image->clsInteger->newInstance();\
   int* presult=(int*)resultObject->dataAt(INT_OBJECT_DATA_OFFSET);\
   *presult=result;\
   return resultObject;\
}
PRIMITIVE_MATHOP_DEF(primitive_mathop_add,lvalue+rvalue);
PRIMITIVE_MATHOP_DEF(primitive_mathop_substract,lvalue-rvalue);
PRIMITIVE_MATHOP_DEF(primitive_mathop_multiply,lvalue*rvalue);
PRIMITIVE_MATHOP_DEF(primitive_mathop_divide,lvalue/rvalue);
PRIMITIVE_MATHOP_DEF(primitive_mathop_mod,lvalue%rvalue);

#define PRIMITIVE_TEST_DEF(name,code)\
Object* name(Image* image,Object* receiver,int argc,Object** argv){\
   if(argc!=1) printf("ERROR: Test methods expect one argument\n");\
   int* plvalue=(int*)receiver->dataAt(INT_OBJECT_DATA_OFFSET);\
   int* prvalue=(int*)argv[0]->dataAt(INT_OBJECT_DATA_OFFSET);\
   int lvalue=*plvalue;\
   int rvalue=*prvalue;\
   bool result=code;\
   Object* resultObject=image->clsBoolean->newInstance();\
   bool* presult=(bool*)resultObject->dataAt(BOOLEAN_OBJECT_DATA_OFFSET);\
   *presult=result;\
   return resultObject;\
}
PRIMITIVE_TEST_DEF(primitive_test_equal,lvalue==rvalue);
PRIMITIVE_TEST_DEF(primitive_test_notEqual,lvalue!=rvalue);
PRIMITIVE_TEST_DEF(primitive_test_greater,lvalue>rvalue);
PRIMITIVE_TEST_DEF(primitive_test_greater_or_equal,lvalue>=rvalue);
PRIMITIVE_TEST_DEF(primitive_test_lesser,lvalue<rvalue);
PRIMITIVE_TEST_DEF(primitive_test_lesser_or_equal,lvalue<=rvalue);

Object* primitive_if(Image* image,Object* self,int argc,Object** argv){
    //if(argc!=2) printf("ERROR: If requires two arguments\n");
    Closure* trueCase=(Closure*)argv[0];
    Closure* falseCase=(Closure*)argv[1];
    if(inspectBoolean(self)){
        return trueCase->execute(image);
    }else{
        return falseCase->execute(image);
    }
}
Object* primitive_do_nothing(Image* image,Object* self,int argc,Object** argv){
    return NULL;
}
Object* primitive_closure_execute(Image* image,Object* self,int argc,Object** argv){
    Closure* c=(Closure*)self;
    c->execute(image,argc,argv);
}
Object* primitive_string_at(Image* image,Object* self,int argc,Object** argv){
    //if(argc!=1) image->exception("String.at requires one argument");
    int len=*((int*)self->dataAt(STRING_OBJECT_LENGTH_OFFSET));
    if(inspectInteger(argv[0])>=len){
        image->exception("ERROR: String index out of bounds");
    }
    char* data=inspectString(self);
    return makeInteger(image,data[inspectInteger(argv[0])]);
}
Object* primitive_string_length(Image* image,Object* self,int argc,Object** argv){
    //if(argc!=0) image->exception("String.length requires zero argument");
    int len=*((int*)self->dataAt(STRING_OBJECT_LENGTH_OFFSET));
    return makeInteger(image,len);
}

Object* primitive_con_string(Image* image,Object* self,int argc,Object** argv){
    //if(argc!=1) image->exception("Console.string requires zero argument");
    printf("%s",inspectString(argv[0]));
    return self;
}
Object* primitive_con_newline(Image* image,Object* self,int argc,Object** argv){
    //if(argc!=0) image->exception("Console.nl requires zero argument");
    printf("\n");
    return self;
}
Object* primitive_array_new(Image* image,Object* self,int argc,Object** argv){
    Class* cls=(Class*)self;
    Object* array=(Object*)image->alloc(cls->getSize()+sizeof(Object*)*inspectInteger(argv[0]));
    cls->initializeObject(array);
    int* length=(int*)array->dataAt(ARRAY_OBJECT_LENGTH_OFFSET);
    //printf("Created array %d\n",inspectInteger(argv[0]));
    *length=inspectInteger(argv[0]);
    Object** data=(Object**)array->dataAt(ARRAY_OBJECT_DATA_OFFSET);
    for(int i=0;i<*length;i++){
        data[i]=NULL;
    }
    return array;
}
Object* primitive_array_at(Image* image,Object* self,int argc,Object** argv){
    //if(argc==1) image->exception("Array.at: requires 2 arguments");
    Object** data=(Object**)self->dataAt(ARRAY_OBJECT_DATA_OFFSET);
    int* length=(int*)self->dataAt(ARRAY_OBJECT_LENGTH_OFFSET);
    if(inspectInteger(argv[0])>=*length || inspectInteger(argv[0]) <0) image->exception("Index out of bounds");
    return data[inspectInteger(argv[0])];
}
Object* primitive_array_at_set(Image* image,Object* self,int argc,Object** argv){
    //if(argc==2) image->exception("Array.at:set: requires 2 arguments");
    Object** data=(Object**)self->dataAt(ARRAY_OBJECT_DATA_OFFSET);
    int* length=(int*)self->dataAt(ARRAY_OBJECT_LENGTH_OFFSET);
    if(inspectInteger(argv[0])>=*length || inspectInteger(argv[0])<0){
        image->exception("Index out of bounds");
    }
    data[inspectInteger(argv[0])]=argv[1];
    return NULL;
}
Object* primitive_array_size(Image* image,Object* self,int argc,Object** argv){
    //if(argc==2) image->exception("Array.at:set: requires 2 arguments");
    int* length=(int*)self->dataAt(ARRAY_OBJECT_LENGTH_OFFSET);
    return makeInteger(image,*length);
}
Object* primitive_array_do(Image* image,Object* self,int argc,Object** argv){
    int* length=(int*)self->dataAt(ARRAY_OBJECT_LENGTH_OFFSET);
    //if(argc==2) image->exception("Array.at:set: requires 2 arguments");
    Object** data=(Object**)self->dataAt(ARRAY_OBJECT_DATA_OFFSET);
    Object* args[2];
    Closure* closure=(Closure*)argv[0];
    for(int i=0;i<*length;i++){
        args[0]=data[i];
        args[1]=makeInteger(image,i);
        Object* res=closure->execute(image,2,args);
        if(res!=NULL) return res;
    }
    return NULL;
}
Object* primitive_array_to_string_as_byte_array(Image* image,Object* self,int argc,Object** argv){
    int length=*((int*)self->dataAt(ARRAY_OBJECT_LENGTH_OFFSET));
    int from=inspectInteger(argv[0]);
    int to=inspectInteger(argv[1]);
    if(from<0 || from >= length || from>to || to>length) image->exception("Illegal subscripts asbytearray");
    Object* r=(Object*)image->alloc(STRING_OBJECT_DATA_OFFSET+(to-from+1));
    image->clsString->initializeObject(r);
    *((int*)r->dataAt(STRING_OBJECT_LENGTH_OFFSET))=to-from;
    char* data=(char*)r->dataAt(STRING_OBJECT_DATA_OFFSET);
    Object** srcData=(Object**)self->dataAt(ARRAY_OBJECT_DATA_OFFSET);
    for(int i=from;i<to;i++){
        data[i-from]=inspectInteger(srcData[i]);
    }
    data[(to-from)]=0;
    return r;
}
Object* primitive_range_do(Image* image,Object* self,int argc,Object** argv){
    int from=inspectInteger(self->get("from"));
    int to=inspectInteger(self->get("to"));
    Closure* closure=(Closure*)argv[0];
    Object* args[1];
    if(from<=to){
        for(int i=from;i<to;i++){
            args[0]=makeInteger(image,i);
            Object* res=closure->execute(image,1,args);
            if(res!=NULL) return res;
        }
    }else if(from>to){
        for(int i=from;i>to;i--){
            args[0]=makeInteger(image,i);
            Object* res=closure->execute(image,1,args);
            if(res!=NULL) return res;
        }
    }
    return NULL;
}
Object* primitive_array_resize(Image* image,Object* self,int argc,Object** argv){
    int length=*((int*)self->dataAt(ARRAY_OBJECT_LENGTH_OFFSET));
    int newLength=inspectInteger(argv[0]);
    //create a new duplicate object, we can not delete the old one
    Object* nw=(Object*)image->alloc(image->clsArray->getSize()+sizeof(Object*)*newLength);
    memcpy(nw,self,image->clsArray->getSize()+sizeof(Object*)*length);
    (*((int*)nw->dataAt(ARRAY_OBJECT_LENGTH_OFFSET)))=newLength;
    return nw;
}
Object* primitive_is_null(Image* image,Object* self,int argc,Object** argv){
    return makeBoolean(image,self==NULL);
}
